package com.naiterui.common.repository.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

import org.hibernate.LockOptions;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.naiterui.common.repository.page.Page;

/**
 * IGenericDao、IEntityDao、INativeQLDao的默认实现类
 * @author zhouyanxin
 * @since 1.0.0
 */
public abstract class DefaultBaseDaoImpl<T, ID extends Serializable> implements IEntityDao<T, ID>, INativeQLDao {

    private static final Pattern PATTERN = Pattern.compile("order\\s+by(\\s?[\\w\\W]\\s?)+?(desc|asc)(\\s*,\\s*[\\w\\W]+(\\s(DESC|ASC))?)*", Pattern.CASE_INSENSITIVE);

    /**
     * HibernateTemplate
     * friendly only package access allow
     */
    HibernateTemplate hibernateTemplate;

    /**
     * JdbcTemplate
     * friendly only package access allow
     */
    JdbcTemplate jdbcTemplate;

    NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    /**
     * 子类Dao所声明的泛型类型(实体域对象的泛型类型)
     */
    private Class<T> entityClass;

    /**
     * 构造函数
     */
    public DefaultBaseDaoImpl() {
        this.initEntityClass();
    }

    /****************************** default template populate *********************************/

    @Autowired
    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
    }

    /****************************** IGenericDaoImpl *********************************/

    @Override
    public T load(ID id) {
        return this.hibernateTemplate.load(this.entityClass, id);
    }

    @Override
    public T get(ID id) {
        return this.hibernateTemplate.get(this.entityClass, id);
    }

    @SuppressWarnings("unchecked")
    @Override
    public ID save(T t) {
        return (ID) this.hibernateTemplate.save(t);
    }

    @Override
    public void update(T t) {
        this.hibernateTemplate.update(t);
    }

    @Override
    public void saveOrUpdate(T t) {
        this.hibernateTemplate.saveOrUpdate(t);
    }

    @Override
    public void remove(T t) {
        this.hibernateTemplate.delete(t);
    }

    @Override
    public void removeById(ID id) {
        this.remove(this.get(id));
    }

    @Override
    public Page<T> pagedQuery(String hql, int pageNo, int pageSize, Object... values) {
        return this.pagedQueryOffset(hql, pageNo, pageSize, 0, values);
    }

    @Override
    public Page<T> pagedQueryOffset(String hql, int pageNo, int pageSize, int offset, Object... values) {
        return this.pagedQueryOffsetForAny(hql, pageNo, pageSize, offset, values);
    }

    /**
     * 支持流式分页的分页查询
     * 该方法也用于子类进行兼容性方法的定义(获取任意类型的对象)
     * 该方法必须保证包内访问
     * DefaultBaseDaoImpl.pagedQueryOffsetForAny()
     */
    @SuppressWarnings("unchecked")
    <E> Page<E> pagedQueryOffsetForAny(String hql, int pageNo, int pageSize, int offset, Object... values) {

        // 查询总记录数
        Long totalCount = this.getCount(hql, values);
        if (totalCount == 0) {
            return Page.buildEmptyPage(pageNo, pageSize, offset);
        }

        // 计算分页参数
        int startIndex = this.calcStartIndex(pageNo, pageSize, offset, totalCount);

        // 查询结果集
        Query<?> query = this.currentSession().createQuery(hql);
        IntStream.range(0, values.length).forEachOrdered(i -> query.setParameter(i, values[i]));
        List<E> list = (List<E>) query.setFirstResult(startIndex).setMaxResults(pageSize).list();

        // 构建分页结果集
        return Page.buildPage(pageNo, pageSize, list, totalCount, offset);
    }

    @Override
    @Deprecated
    @SuppressWarnings("unchecked")
    public List<T> findByLimitWithOffset(String hql, int startIndex, int limit, Object... values) {
        return (List<T>) this.hibernateTemplate.execute(session -> {
            Query<?> query = session.createQuery(hql);
            IntStream.range(0, values.length).forEachOrdered(i -> query.setParameter(i, values[i]));
            query.setFirstResult(startIndex).setMaxResults(limit);
            return query.list();
        });
    }

    @Override
    public Long getCount(String hql, Object... values) {
        final String chql = "select count(*) " + this.removeSelect(this.removeOrders(hql));
        return this.hibernateTemplate.execute(session -> {
            Query<Long> query = session.createQuery(chql, Long.class);
            IntStream.range(0, values.length).forEachOrdered(i -> query.setParameter(i, values[i]));
            return query.getResultList().get(0);
        });
    }

    @Override
    public T findOne(String hql, Object... values) {
        List<T> result = this.find(hql, values);
        if (CollectionUtils.isEmpty(result)) {
            return null;
        }
        if (result.size() != 1) {
            throw new NonUniqueResultException(result.size());
        }
        return result.get(0);
    }

    @Override
    public List<T> find(String hql, Object... values) {
        return this.findByPage(hql, 0, 0, values);
    }

    @Override
    public List<T> findByPage(String hql, int pageNo, int pageSize, Object... values) {
        return this.findForAny(hql, pageNo, pageSize, null, values);
    }

    @Override
    @Deprecated
    public List<T> findByLimit(String hql, int limit, Object... values) {
        return this.findByPage(hql, 1, limit, values);
    }

    /**
     * 支持锁及分页参数的HQL查询
     * 用于对IGenericDao中几个find方法的逻辑进行复用
     * 该方法也用于子类进行兼容性方法的定义(获取任意类型的对象)
     * 该方法必须保证包内访问
     */
    @SuppressWarnings({ "unchecked", "WeakerAccess" })
    <E> List<E> findForAny(String hql, int page, int pageSize, LockOptions lock, Map<String, Object> paramMap) {
        return (List<E>) this.hibernateTemplate.execute(session -> {
            Query<?> query = session.createQuery(hql);
            if (page > 0 && pageSize > 0) {
                int startIndex = this.calcStartIndex(page, pageSize, 0, 0);
                query.setFirstResult(startIndex).setMaxResults(pageSize);
            }
            if (lock != null) {
                query.setLockOptions(lock);
            }
            paramMap.forEach(query::setParameter);
            return query.list();
        });
    }

    /**
     * 支持锁及分页参数的HQL查询
     * 用于对IGenericDao中几个find方法的逻辑进行复用
     * 该方法也用于子类进行兼容性方法的定义(获取任意类型的对象)
     * 该方法必须保证包内访问
     * DefaultBaseDaoImpl.find()
     *
     * @param hql      需要执行的HQL语句
     * @param page     当前页
     * @param pageSize 每页显示记录数
     * @param lock     加锁方式 {@link LockOptions}
     * @param values   查询条件参数注入
     */
    @SuppressWarnings("unchecked")
    <E> List<E> findForAny(String hql, int page, int pageSize, LockOptions lock, Object... values) {
        if (values != null && values.length == 1 && values[0] instanceof Map) {
            return this.findForAny(hql, page, pageSize, lock, (Map) values[0]);
        }
        return (List<E>) this.hibernateTemplate.execute(session -> {
            Query<?> query = session.createQuery(hql);
            if (page > 0 && pageSize > 0) {
                int startIndex = this.calcStartIndex(page, pageSize, 0, 0);
                query.setFirstResult(startIndex).setMaxResults(pageSize);
            }
            if (lock != null) {
                query.setLockOptions(lock);
            }
            IntStream.range(0, values.length).forEachOrdered(i -> query.setParameter(i + 1, values[i]));
            return query.list();
        });
    }

    /****************************** IEntityDaoImpl *********************************/

    @Override
    public List<T> loadAll() {
        return this.hibernateTemplate.loadAll(this.entityClass);
    }

    @Override
    public List<T> findByLock(String hql, Object... values) {
        return this.findForAny(hql, 0, 0, LockOptions.UPGRADE, values);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> findByCriteria(DetachedCriteria criteria) {
        return (List<T>) this.hibernateTemplate.findByCriteria(criteria);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T findByCriteriaUnique(DetachedCriteria criteria) {
        return (T) criteria.getExecutableCriteria(this.currentSession()).uniqueResult();
    }

    @Override
    public Long getSqlCount(String sql, Object... values) {
        String countQueryString = " select COUNT(*) " + this.removeSelect(this.removeOrders(sql));
        return this.jdbcTemplate.queryForObject(countQueryString, values, Long.class);
    }

    @Override
    public Long getSqlCount(String sql, Map<String, ?> paramMap) {
        String countQueryString = " select COUNT(*) " + this.removeSelect(this.removeOrders(sql));
        return this.namedParameterJdbcTemplate.queryForObject(countQueryString, paramMap, Long.class);
    }

    /****************************** INativeQLDaoImpl *********************************/

    @Override
    public Query<?> getSQLQuery(String sql, Object... values) {
        NativeQuery<?> query = this.currentSession().createNativeQuery(sql);
        // 这里下标是从1开始的
        IntStream.range(0, values.length).forEachOrdered(i -> query.setParameter(i + 1, values[i]));
        return query;
    }

    @Override
    public <E> List<E> sqlQuery(Class<E> requiredType, String sql, Object... values) {
        // 2018/7/26 上午10:24 王泽浩 这里需要判断class是不是包装类型
        Assert.notNull(requiredType, "映射类型不能为null");
        Class<?>[] interfaces = requiredType.getInterfaces();
        //判断requiredType是不是实现了Map，List，Set接口，若果是，那么不能返回数据,查询出来的是空数据
        Arrays.stream(interfaces).filter(anInterface -> Map.class == anInterface || List.class == anInterface || Set.class == anInterface).forEachOrdered(anInterface -> {
            throw new IllegalArgumentException("Class must not be " + requiredType.getName());
        });
        RowMapper<E> rowMapper;
        //这里的if来源于JdbcUtils.getResultSetValue
        if (String.class == requiredType ||
                boolean.class == requiredType || Boolean.class == requiredType ||
                byte.class == requiredType || Byte.class == requiredType ||
                short.class == requiredType || Short.class == requiredType ||
                int.class == requiredType || Integer.class == requiredType ||
                long.class == requiredType || Long.class == requiredType ||
                float.class == requiredType || Float.class == requiredType ||
                double.class == requiredType || Double.class == requiredType ||
                Number.class == requiredType || BigDecimal.class == requiredType || java.sql.Date.class == requiredType ||
                java.sql.Time.class == requiredType || java.sql.Timestamp.class == requiredType || java.util.Date.class == requiredType || byte[].class == requiredType || Blob.class == requiredType || Clob.class == requiredType || requiredType.isEnum()) {
            rowMapper = new SingleColumnRowMapper<>(requiredType);
        } else {
            rowMapper = new BeanPropertyRowMapper<>(requiredType);
        }
        return this.jdbcTemplate.query(sql, values, rowMapper);
    }

    @Override
    @Deprecated
    public <E> List<E> sqlQuery2Bean(Class<E> clazz, String sql, Object... values) {
        return this.sqlQuery(clazz, sql, values);
    }

    @Override
    public int executeSQLResult(String sql, Object... values) {
        return this.jdbcTemplate.update(sql, values);
    }

    @Override
    public int executeSQLResult(String sql, Map<String, ?> paramMap) {
        return this.namedParameterJdbcTemplate.update(sql, paramMap);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <E> List<E> findBySQLQuery(String sql, Object... values) {
        return (List<E>) this.getSQLQuery(sql, values).list();
    }

    @Override
    public void executeSQL(String sql, Object... values) {
        this.executeSQLResult(sql, values);
    }

    @Override
    @Deprecated
    public void executeSQL(String sql) {
        this.executeSQLResult(sql);
    }

    @Override
    public void batch(String sql, List<Object[]> parameters) {
        this.currentSession().doWork(connection -> {
            PreparedStatement statement = connection.prepareStatement(sql);
            for (int i = 0; i < parameters.size(); i++) {
                Object[] params = parameters.get(i);
                for (int j = 0; j < params.length; j++) {
                    statement.setObject(j + 1, params[j]);
                }
                statement.addBatch();
                if (i % 50 == 0) {
                    statement.executeBatch();
                }
            }
            statement.executeBatch();
        });
    }

    @Override
    public <E> Page<E> pagedSqlQuery(Class<E> clazz, String sql, int pageNo, int pageSize, Object... values) {
        return this.pagedSqlQueryOffset(clazz, sql, pageNo, pageSize, 0, values);
    }

    @Override
    public <E> Page<E> pagedSqlQueryOffset(Class<E> clazz, String sql, int pageNo, int pageSize, int offset, Object... values) {
        return this.pagedQueryBySql(clazz, null, sql, pageNo, pageSize, offset, values);
    }

    @Deprecated
    @Override
    public <E> Page<E> pagedSqlQueryAliasToBean(Class<E> clazz, Map<String, org.hibernate.type.Type> scalarMap, String sql, int pageNo, int pageSize,
            Object... values) {
        return this.pagedQueryBySql(clazz, scalarMap, sql, pageNo, pageSize, 0, values);
    }

    /**
     * SQL查询支持分页，并且根据指定的scalar集合进行ORM映射
     * DefaultBaseDaoImpl.pagedQueryBySql()
     */
    @SuppressWarnings("unchecked")
    private <E> Page<E> pagedQueryBySql(Class<E> clazz, Map<String, org.hibernate.type.Type> scalarMap, String sql, int pageNo, int pageSize,
            int offset, Object... values) {

        // Count查询
        String countQueryString = " select COUNT(*) " + this.removeSelect(this.removeOrders(sql));
        Long totalCount = this.jdbcTemplate.queryForObject(countQueryString, values, Long.class);
        if (totalCount == null || totalCount == 0) {
            return Page.buildEmptyPage(pageNo, pageSize, offset);
        }

        // 计算分页参数
        int startIndex = this.calcStartIndex(pageNo, pageSize, offset, totalCount);
        List<Object> params = new ArrayList<>(Arrays.asList(values));
        params.add(startIndex);
        params.add(pageSize);

        // 拼接limit
        String limitSql = sql + " limit ?,?";

        // 实际查询返回分页对象
        NativeQuery<E> sqlQuery = (NativeQuery<E>) this.getSQLQuery(limitSql, values);

        if (scalarMap != null) {
            scalarMap.forEach(sqlQuery::addScalar);
        }
        List<E> list = this.sqlQuery(clazz, limitSql, params.toArray());

        // 分页结果集
        return Page.buildPage(pageNo, pageSize, list, totalCount, offset);
    }

    ////////////////////////////////// 公共私有方法 //////////////////////////////////

    /**
     * 获取当前Session对象
     * friendly only package access allow
     * DefaultBaseDaoImpl.currentSession()
     * @author zhouyanxin
     * @date 2018年5月11日
     * @since 1.0.0
     * @return
     */
    public Session currentSession() {
        return this.hibernateTemplate.getSessionFactory().getCurrentSession();
    }

    /**
     * 初始化当前Dao实例所绑定的实体域对象的class类型
     * 子类Dao定义的泛型
     * DefaultBaseDaoImpl.initEntityClass()
     * @author zhouyanxin
     * @date 2018年5月11日
     * @since 1.0.0
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void initEntityClass() {
        Type genType = this.getClass().getGenericSuperclass();
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        this.entityClass = (Class) params[0];
    }

    /**
     * 移除当前hql语句中select的部分
     * DefaultBaseDaoImpl.removeSelect()
     * @author zhouyanxin
     * @date 2018年5月11日
     * @since 1.0.0
     * @param hql
     * @return
     */
    private String removeSelect(String hql) {
        int beginPos = hql.toLowerCase().indexOf("from");
        return hql.substring(beginPos);
    }

    /**
     * 移除当前hql语句中的排序内容
     * DefaultBaseDaoImpl.removeOrders()
     * @author zhouyanxin
     * @date 2018年5月11日
     * @since 1.0.0
     * @param hql
     * @return
     */
    private String removeOrders(String hql) {
        Matcher m = PATTERN.matcher(hql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    /**
     * 计算分页查询的起始偏移量
     * DefaultBaseDaoImpl.calcStartIndex()
     * @Author zhouyanxin
     * @Date 2018年6月7日
     * @since 1.0.0
     * @param pageNo
     * @param pageSize
     * @param offset
     * @param totalCount
     * @return
     */
    private int calcStartIndex(int pageNo, int pageSize, long offset, long totalCount) {

        // 计算总页数
        long total = totalCount;
        if (offset > 0 && offset > totalCount) {
            total = offset;
        }

        long totalPage = total / pageSize;
        if (total % pageSize > 0) {
            totalPage++;
        }

        // 如果获取的分页范围大于总页数则默认最后一页
        if (totalPage < pageNo) {
            pageNo = (int) totalPage;
        }

        // 实际查询返回分页对象
        long startIndex = (pageNo - 1) * pageSize;

        // offset > 0 启用流式分页，按第一页生成的总记录数减最新总记录数，计算偏移下页起始记录偏移值
        if (offset > 0 && offset > totalCount) {
            startIndex = startIndex - (offset - totalCount);
        }
        // else {
        // offset = totalCount;
        // }

        return (int) startIndex;
    }

    @Override
    public DetachedCriteria createDetachedCriteria() {
        return null;
    }

    @Override
    public Page<T> pagedQuery(int pageNo, int pageSize, DetachedCriteria detachedCriteria) {
        return null;
    }

    @Override
    public Page<T> pagedQuery(int pageNo, int pageSize, Criterion... criterions) {
        return null;
    }
}
